import React, { useState, useEffect, useMemo, useRef } from 'react'; import { Home, MapPin, Settings, Search, Bell, Activity, ClipboardList, Calendar, CheckCircle, Clock, AlertTriangle, Wind, Droplet, ShieldAlert, Users, FileText, Database, DownloadCloud, Check, RefreshCw, Info, Printer, ShieldCheck, ShieldX, Map as MapIcon, ListFilter, ArrowRight, Crosshair, Edit, Target, PlayCircle, Eye, User, AlertCircle, Home as HomeIcon, BellRing, Bug, Lightbulb, Timer, Flame, UploadCloud, ExternalLink, FolderOpen, BarChart2, ChevronUp, Filter, Camera, Menu, X, Download } from 'lucide-react'; const parseOnsetObject = (dateStr) => { if (!dateStr || dateStr === 'ไม่ระบุวันที่' || dateStr === '-') return null; if (dateStr instanceof Date) return dateStr; if (typeof dateStr === 'string') { const timestampDate = new Date(dateStr); if (!isNaN(timestampDate) && dateStr.includes('T')) return timestampDate; if (dateStr.includes('-')) { const d = new Date(dateStr); if (!isNaN(d)) return d; } if (dateStr.includes('/')) { const parts = dateStr.split('/'); if (parts.length === 3) { let year = parseInt(parts[2]); if (year > 2500) year -= 543; return new Date(year, parseInt(parts[1]) - 1, parseInt(parts[0])); } } const fallbackDate = new Date(dateStr); if (!isNaN(fallbackDate)) return fallbackDate; } return null; }; const formatThaiDate = (dateInput) => { if (!dateInput) return '-'; const d = parseOnsetObject(dateInput); if (!d || isNaN(d)) return typeof dateInput === 'string' ? dateInput : '-'; const day = d.getDate(); const monthNames = ["ม.ค.", "ก.พ.", "มี.ค.", "เม.ย.", "พ.ค.", "มิ.ย.", "ก.ค.", "ส.ค.", "ก.ย.", "ต.ค.", "พ.ย.", "ธ.ค."]; return `${day} ${monthNames[d.getMonth()]} ${d.getFullYear() + 543}`; }; const formatDateTimeThai = (dateString) => { if (!dateString) return '-'; const d = new Date(dateString); if (isNaN(d)) return '-'; const day = d.getDate(); const monthNames = ["ม.ค.", "ก.พ.", "มี.ค.", "เม.ย.", "พ.ค.", "มิ.ย.", "ก.ค.", "ส.ค.", "ก.ย.", "ต.ค.", "พ.ย.", "ธ.ค."]; const month = monthNames[d.getMonth()]; const year = d.getFullYear() + 543; const hours = String(d.getHours()).padStart(2, '0'); const mins = String(d.getMinutes()).padStart(2, '0'); return `${day} ${month} ${year} เวลา ${hours}:${mins} น.`; }; const GOOGLE_FORM_URL = "https://docs.google.com/forms/d/e/1FAIpQLSc4ggD3F1yHaKRvAM38WeTt5dw1_86efa-HsHknxeiTRlL-WQ/viewform"; const GOOGLE_SHEET_URL = "https://docs.google.com/spreadsheets/d/1SV9KyyKne4quhDzBj5k0LGC6A5TiOlXmVVtyEgYWqcw/edit?resourcekey=&gid=67922063#gid=67922063"; const GOOGLE_SCRIPT_URL = 'https://script.google.com/macros/s/AKfycby5m3Jr_lswIjob7Zprp9K2w449RJFxPMa3gjknMtB2849oiCB0_auejMqj3yW-Fh-H/exec'; const App = () => { const [activeMenu, setActiveMenu] = useState('patients'); const [selectedPatient, setSelectedPatient] = useState(null); const [activePhase, setActivePhase] = useState('3h1'); const [fetchedCases, setFetchedCases] = useState([]); const [isLoadingCases, setIsLoadingCases] = useState(true); const [isSubmitting, setIsSubmitting] = useState(false); const [searchTerm, setSearchTerm] = useState(''); const [selectedYear, setSelectedYear] = useState('latest'); const [formData, setFormData] = useState({}); const [isLoadingHistory, setIsLoadingHistory] = useState(false); const [editMode, setEditMode] = useState(false); const [trackingHistory, setTrackingHistory] = useState({}); const [expandedKpi, setExpandedKpi] = useState(null); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const [isDownloadingImage, setIsDownloadingImage] = useState(false); const summaryRef = useRef(null); const initialSteps = [ { id: '3h1', label: 'Day 0 (รับแจ้ง)', desc: 'รายงาน รพ.สต.', targetDays: 0 }, { id: '3h2', label: 'Day 0 (ลงพื้นที่)', desc: 'สเปรย์/สำรวจ', targetDays: 0 }, { id: '1d', label: 'Day 1', desc: 'พ่นครั้งที่ 1', targetDays: 1 }, { id: '3d', label: 'Day 3', desc: 'พ่นครั้งที่ 2', targetDays: 3 }, { id: '7d', label: 'Day 7', desc: 'พ่นครั้งที่ 3', targetDays: 7 }, { id: '14d', label: 'Day 14', desc: 'สำรวจลูกน้ำ', targetDays: 14 }, { id: '21d', label: 'Day 21', desc: 'สำรวจลูกน้ำ', targetDays: 21 }, { id: '28d', label: 'Day 28', desc: 'ประเมิน 28 วัน', targetDays: 28 }, { id: 'summary', label: 'สรุปภาพรวม', desc: 'รายงานปิดเคส', targetDays: 28 }, ]; // 🌟 อัปเดตแนวทางปฏิบัติตามมาตรฐานกระทรวงสาธารณสุขเป๊ะๆ const phaseGuidelines = { '3h1': 'ภายใน 3 ชั่วโมงแรก สถานบริการสาธารณสุขรายงานโรคให้ รพ.สต. หรือศูนย์บริการสาธารณสุขในพื้นที่ทราบ', '3h2': 'ภายใน 3 ชั่วโมงหลังได้รับรายงาน รพ.สต. หรือศูนย์บริการสาธารณสุขในพื้นที่ สอบสวนโรค ทำลายแหล่งเพาะพันธุ์ยุงลาย ฉีดสเปรย์กระป๋องกำจัดยุงในบ้านผู้ป่วย และจ่ายยาทากันยุงให้ผู้ป่วยรวมถึงกลุ่มเสี่ยง', '1d': '1 วันหลังได้รับรายงาน อสม./อสส. สำรวจและทำลายแหล่งเพาะพันธุ์ยุงลาย และ อปท. พ่นสารเคมีกำจัดยุง ที่บ้านผู้ป่วยและบ้านที่อยู่ในรัศมี 100 เมตร ทั้งในและนอกบ้าน และจุดที่สงสัยเป็นแหล่งโรค', '3d': '3 วันหลังได้รับรายงาน อปท. พ่นสารเคมีกำจัดยุง ที่บ้านผู้ป่วยและบ้านที่อยู่ในรัศมี 100 เมตร ทั้งในและนอกบ้าน และจุดที่สงสัยเป็นแหล่งโรค', '7d': '7 วันหลังได้รับรายงาน อสม./อสส. สำรวจและทำลายแหล่งเพาะพันธุ์ยุงลาย และ อปท. พ่นสารเคมีกำจัดยุง รัศมี 100 เมตร (เป้าหมาย HI และ CI ในรัศมี 100 เมตรจากบ้านผู้ป่วยต้องเป็นศูนย์)', '14d': '14 วันหลังได้รับรายงาน อสม./อสส. สำรวจและทำลายแหล่งเพาะพันธุ์ยุงลายทั้งหมู่บ้าน เฝ้าระวังและติดตามผู้ป่วยรุ่นที่ 2 (เป้าหมาย HI ในหมู่บ้านไม่เกินร้อยละ 5)', '21d': '21 วันหลังได้รับรายงาน อสม./อสส. สำรวจและทำลายแหล่งเพาะพันธุ์ยุงทั้งหมู่บ้าน เฝ้าระวังและติดตามผู้ป่วยรุ่นที่ 2 (เป้าหมาย HI หมู่บ้านไม่เกิน 5%, CI สถานพยาบาล/โรงเรียน เป็น 0, CI โรงธรรม/โรงแรม/โรงงาน/สถานที่ราชการ ไม่เกิน 5%)', '28d': '28 วันหลังได้รับรายงาน คงมาตรการสำรวจและทำลายแหล่งเพาะพันธุ์ยุงในชุมชน ทุก 7 วัน โดยให้ชุมชนมีส่วนร่วม และเฝ้าระวัง ติดตามผู้ป่วยรุ่นที่ 2 (หลังจาก 28 วัน ทบทวนและถอดบทเรียนการดำเนินมาตรการฯ)', 'summary': 'สรุปผลการดำเนินงานตั้งแต่ Day 0 ถึง Day 28 เพื่อใช้ประกอบการรายงานสอบสวนโรคเบื้องต้น' }; const [steps, setSteps] = useState(initialSteps.map(s => ({ ...s, status: 'pending', date: '-' }))); useEffect(() => { const fetchData = async () => { try { setIsLoadingCases(true); const csvPromise = fetch('https://docs.google.com/spreadsheets/d/e/2PACX-1vQI5kThGnr-ba-HnQQRD7iSSZkloNQf3CVQ5omvq0sgzd1_OVXoGU9Y4GJm5d9Ae7Nbvh_E29VTAEY_/pub?output=csv').then(res => res.text()); const trackingPromise = fetch(`${GOOGLE_SCRIPT_URL}?action=getAllWithData`).then(res => res.json()).catch(() => ({ status: 'error', data: [] })); const [csvText, trackingResult] = await Promise.all([csvPromise, trackingPromise]); const trackData = {}; if (trackingResult?.status === 'success' && trackingResult.data) { trackingResult.data.forEach(row => { if (!trackData[row.hn]) trackData[row.hn] = {}; trackData[row.hn][row.phase] = row; }); } setTrackingHistory(trackData); const lines = csvText.split('\n').filter(line => line.trim() !== ''); if (lines.length === 0) throw new Error("CSV is empty"); const headers = lines[0].split(',').map(h => h.trim().replace(/^"|"$/g, '')); const hnIdx = headers.findIndex(h => h.includes('HN') || h.includes('รหัส')); const ageIdx = headers.findIndex(h => h.includes('อายุ')); const dateIdx = headers.findIndex(h => h.includes('เริ่มป่วย') || h.includes('Onset')); const subdistrictIdx = headers.findIndex(h => h.includes('ตำบล')); const villageIdx = headers.findIndex(h => h.includes('หมู่บ้าน') || h.includes('หมู่ที่')); const addressIdx = headers.findIndex(h => h.includes('บ้านเลขที่') || h.includes('ที่อยู่')); const latIdx = headers.findIndex(h => h.toLowerCase() === 'lat' || h.includes('ละติจูด')); const lngIdx = headers.findIndex(h => h.toLowerCase() === 'lng' || h.includes('ลองติจูด')); const parsedData = lines.slice(1).map((line, idx) => { const values = line.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/).map(v => v.replace(/^"|"$/g, '').trim()); const rawDate = values[dateIdx] || ''; const onsetDateObj = parseOnsetObject(rawDate); let daysPassed = 0; let endDateStr = '-'; if (onsetDateObj) { daysPassed = Math.floor((new Date().getTime() - onsetDateObj.getTime()) / (1000 * 60 * 60 * 24)); const endDate = new Date(onsetDateObj); endDate.setDate(endDate.getDate() + 28); endDateStr = formatThaiDate(endDate); } let caseYear = 'ไม่ระบุ'; if (rawDate) { const matchY = rawDate.match(/\b(25\d{2}|20\d{2})\b/); if (matchY) caseYear = matchY[1]; } let rawHN = values[hnIdx] || ''; if (!rawHN) { const shortYear = caseYear !== 'ไม่ระบุ' ? caseYear.toString().slice(-2) : new Date().getFullYear().toString().slice(-2); rawHN = `${shortYear}-${String(idx+1).padStart(3, '0')}`; } const patientPhases = Object.keys(trackData[rawHN] || {}); let realStatus = 'รอบันทึกข้อมูล'; if (patientPhases.length > 0) { realStatus = (patientPhases.includes('28d') || patientPhases.includes('summary')) ? 'เสร็จสิ้นการเฝ้าระวัง' : 'ระหว่างการเฝ้าระวัง'; } return { id: `${rawHN}-${idx}`, displayHn: rawHN, age: values[ageIdx] || '0', date: formatThaiDate(onsetDateObj), rawDate: rawDate, onsetDateObj: onsetDateObj, endDate: endDateStr, daysSinceOnset: Math.max(0, daysPassed), address: `${values[addressIdx] || ''} ${values[villageIdx] || ''} ต.${values[subdistrictIdx] || ''}`, village: values[villageIdx] || '', subdistrict: values[subdistrictIdx] || '', lat: parseFloat(values[latIdx]) || (15.93 + (Math.random() - 0.5) * 0.05), lng: parseFloat(values[lngIdx]) || (104.91 + (Math.random() - 0.5) * 0.05), status: realStatus, year: caseYear, completedPhases: patientPhases }; }); setFetchedCases(parsedData); } catch (err) { console.warn("Error fetching Data:", err.message); } finally { setIsLoadingCases(false); } }; fetchData(); }, []); const availableYears = useMemo(() => [...new Set(fetchedCases.map(p => p.year))].filter(y => y !== 'ไม่ระบุ').sort().reverse(), [fetchedCases]); useEffect(() => { if (selectedYear === 'latest' && availableYears.length > 0) { setSelectedYear(availableYears[0]); } }, [availableYears, selectedYear]); const kpiStats = useMemo(() => { const yearCases = fetchedCases.filter(p => selectedYear === 'all' || selectedYear === 'latest' ? true : p.year === selectedYear); const totalCases = yearCases.length; // KPI 2: Complete Tracking 28 Days const past28DaysCases = yearCases.filter(c => c.daysSinceOnset > 28); let completeTracking = 0; past28DaysCases.forEach(c => { const p = c.completedPhases; if (p.includes('1d') && p.includes('3d') && p.includes('7d') && (p.includes('28d') || p.includes('summary'))) { completeTracking++; } }); const kpi2 = past28DaysCases.length === 0 ? 0 : ((completeTracking / past28DaysCases.length) * 100); // KPI 3: No new cases in 28 days let noNewCasesCount = 0; past28DaysCases.forEach(c => { const vCases = yearCases.filter(other => other.subdistrict === c.subdistrict && other.village === c.village && other.id !== c.id); let hasNewWithin28 = false; vCases.forEach(other => { if (c.onsetDateObj && other.onsetDateObj) { const diffDays = (other.onsetDateObj.getTime() - c.onsetDateObj.getTime()) / (1000 * 60 * 60 * 24); if (diffDays > 0 && diffDays <= 28) hasNewWithin28 = true; } }); if (!hasNewWithin28) noNewCasesCount++; }); const kpi3 = past28DaysCases.length === 0 ? 0 : ((noNewCasesCount / past28DaysCases.length) * 100); // KPI 4: Gen 2 Rate (Day 15-28) let gen2CasesCount = 0; yearCases.forEach(c => { const vCases = yearCases.filter(other => other.subdistrict === c.subdistrict && other.village === c.village && other.id !== c.id); let isGen2 = false; vCases.forEach(other => { if (c.onsetDateObj && other.onsetDateObj) { const diffDays = (c.onsetDateObj.getTime() - other.onsetDateObj.getTime()) / (1000 * 60 * 60 * 24); if (diffDays >= 15 && diffDays <= 28) isGen2 = true; } }); if (isGen2) gen2CasesCount++; }); const kpi4 = totalCases === 0 ? 0 : ((gen2CasesCount / totalCases) * 100); // KPI 5: Recurrent Larvae Rate let recurrentEarlyCases = 0; let surveyedEarlyCases = 0; yearCases.forEach(c => { const history = trackingHistory[c.displayHn] || {}; const earlyPhases = ['3h2', '1d', '3d', '7d']; let allHouses = []; let hasRecurrent = false; let surveyCount = 0; earlyPhases.forEach(p => { const hData = history[p]?.formData?.housesFound100m; if (history[p]) surveyCount++; if (hData) { const houses = hData.split(/[\s,]+/).map(s => s.trim()).filter(s => s.length > 0); houses.forEach(h => { if (allHouses.includes(h)) hasRecurrent = true; allHouses.push(h); }); } }); if (surveyCount >= 2) { surveyedEarlyCases++; if (hasRecurrent) recurrentEarlyCases++; } }); const kpi5 = surveyedEarlyCases === 0 ? 0 : ((recurrentEarlyCases / surveyedEarlyCases) * 100); return { kpi2: { val: kpi2.toFixed(1), num: completeTracking, den: past28DaysCases.length, unit: 'ราย' }, kpi3: { val: kpi3.toFixed(1), num: noNewCasesCount, den: past28DaysCases.length, unit: 'ราย' }, kpi4: { val: kpi4.toFixed(1), num: gen2CasesCount, den: totalCases, unit: 'ราย' }, kpi5: { val: kpi5.toFixed(1), num: recurrentEarlyCases, den: surveyedEarlyCases, unit: 'พื้นที่' }, }; }, [fetchedCases, trackingHistory, selectedYear]); const filteredPatients = useMemo(() => { return fetchedCases.filter(p => { const matchSearch = p.displayHn.toLowerCase().includes(searchTerm.toLowerCase()) || p.address.includes(searchTerm); const matchYear = (selectedYear === 'all' || selectedYear === 'latest') ? true : p.year === selectedYear; return matchSearch && matchYear; }); }, [fetchedCases, searchTerm, selectedYear]); const systemAlerts = useMemo(() => { const alerts = { overdue: [], dueToday: [], kpiFailed: [], outbreaks: [] }; const currentThaiYear = (new Date().getFullYear() + 543).toString(); const subdistrictStats = {}; const currentYearCases = fetchedCases .filter(c => c.year === currentThaiYear) .sort((a, b) => (a.onsetDateObj?.getTime() || 0) - (b.onsetDateObj?.getTime() || 0)); currentYearCases.forEach(c => { const vKey = `${c.subdistrict}|${c.village}`; if (!subdistrictStats[vKey]) subdistrictStats[vKey] = { cases: [], duration: 0 }; const stats = subdistrictStats[vKey]; const lastCase = stats.cases[stats.cases.length - 1]; if (lastCase && c.onsetDateObj && lastCase.onsetDateObj) { const gap = Math.floor((c.onsetDateObj.getTime() - lastCase.onsetDateObj.getTime()) / (1000 * 60 * 60 * 24)); if (gap <= 28) { stats.duration += gap; if (stats.duration > 28 && !alerts.outbreaks.includes(vKey.replace('|', ' ต.'))) { alerts.outbreaks.push(vKey.replace('|', ' ต.')); } } else { stats.duration = 0; } } stats.cases.push(c); if (c.status !== 'เสร็จสิ้นการเฝ้าระวัง') { const dso = c.daysSinceOnset; const nextRequiredPhase = initialSteps.find(s => s.id !== 'summary' && s.targetDays <= dso && !c.completedPhases.includes(s.id)); if (nextRequiredPhase) { const expectedDays = nextRequiredPhase.targetDays; const isOverdue = dso > expectedDays; const alertObj = { hn: c.displayHn, village: c.village, phaseName: nextRequiredPhase.label, daysPassed: dso, expectedDay: expectedDays, patient: c }; if (isOverdue) alerts.overdue.push(alertObj); else if (dso === expectedDays) alerts.dueToday.push(alertObj); } } const pHistory = trackingHistory[c.displayHn]; if (pHistory) { if (pHistory['7d']) { const fd = pHistory['7d'].formData; const hi100 = (fd?.survey100mFound / fd?.survey100mTotal) * 100 || 0; if (hi100 > 0) alerts.kpiFailed.push({ hn: c.displayHn, village: c.village, issue: `Day 7: HI 100ม. = ${hi100.toFixed(1)}% (เป้า 0%)` }); } if (pHistory['14d']) { const fd = pHistory['14d'].formData; const hiVillage = (fd?.surveyCommFound / fd?.surveyCommTotal) * 100 || 0; if (hiVillage > 5) alerts.kpiFailed.push({ hn: c.displayHn, village: c.village, issue: `Day 14: HI หมู่บ้าน = ${hiVillage.toFixed(1)}% (เป้า < 5%)` }); } } }); return alerts; }, [fetchedCases, trackingHistory]); useEffect(() => { if (activeMenu !== 'map' || isLoadingCases) return; let mapInstance = null; const initMap = async () => { if (!document.getElementById('leaflet-css')) { const link = document.createElement('link'); link.id = 'leaflet-css'; link.rel = 'stylesheet'; link.href = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css'; document.head.appendChild(link); } if (!window.L) { const script = document.createElement('script'); script.src = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js'; document.head.appendChild(script); await new Promise(resolve => script.onload = resolve); } const container = document.getElementById('real-leaflet-map'); if (container && !container._leaflet_id) { mapInstance = window.L.map('real-leaflet-map'); window.L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', { attribution: 'Tiles © Esri' }).addTo(mapInstance); const bounds = window.L.latLngBounds([]); filteredPatients.forEach(patient => { const latLng = [patient.lat, patient.lng]; bounds.extend(latLng); const isCompleted = patient.status === 'เสร็จสิ้นการเฝ้าระวัง'; const isPending = patient.status === 'รอบันทึกข้อมูล'; const circleColor = isCompleted ? '#10B981' : (isPending ? '#EF4444' : '#F59E0B'); const markerColor = isCompleted ? '#059669' : (isPending ? '#B91C1C' : '#D97706'); window.L.circle(latLng, { color: circleColor, fillColor: circleColor, fillOpacity: 0.3, weight: 2, radius: 100 }).addTo(mapInstance); const marker = window.L.circleMarker(latLng, { radius: 5, color: '#ffffff', weight: 2, fillColor: markerColor, fillOpacity: 1 }).addTo(mapInstance); const popupContent = `
เยี่ยมมาก! ไม่มีกิจกรรมค้างดำเนินการ และพื้นที่ระบาดอยู่ในเกณฑ์ควบคุมได้
ไม่มีภารกิจค้าง
: systemAlerts.overdue.map((a, i) => (ไม่มีภารกิจวันนี้
: systemAlerts.dueToday.map((a, i) => (ทุกพื้นที่อยู่ในเกณฑ์ปกติ
}| รหัสผู้ป่วย / อายุ | หมู่บ้าน/ตำบล | วันที่เริ่มป่วย (Onset) | ระยะเวลาเฝ้าระวัง | สถานะเคส | จัดการ |
|---|---|---|---|---|---|
กำลังโหลดข้อมูลผู้ป่วย... | |||||
| ไม่พบข้อมูลผู้ป่วยที่ค้นหา | |||||
|
{p.displayHn}
อายุ {p.age} ปี
|
{p.village}
|
{p.daysSinceOnset <= 28 ? `เป้า 28 วัน: ${p.endDate}` : `ปิดเคสเมื่อ: ${p.endDate}`}
|
{p.daysSinceOnset <= 28 ? (
Day {p.daysSinceOnset} / 28
) : (
สิ้นสุดเฝ้าระวัง
)}
{p.status !== 'เสร็จสิ้นการเฝ้าระวัง' && p.daysSinceOnset <= 28 && (isOverdue || isDueToday) && (
{isOverdue && เกินกำหนด}
{isDueToday && ต้องทำวันนี้}
)}
|
{p.status === 'เสร็จสิ้นการเฝ้าระวัง' && |
|
แสดงพิกัดที่อยู่อาศัยและวงจรเฝ้าระวัง 100 เมตร (ดาวเทียม)
อ้างอิงพ่นหมอกควันและกำจัดแหล่งเพาะพันธุ์
อายุผู้ป่วย
{selectedPatient.age} ปี
บ้าน{selectedPatient.village} ต.{selectedPatient.subdistrict}
{selectedPatient.date}
ระยะเวลาผ่านไป
Day {selectedPatient.daysSinceOnset}
เป้าหมาย 28 วัน
ช่วงวิกฤต 7 วันแรก (Critical Transmission Period)
ผู้ป่วยยังอยู่ในระยะแพร่เชื้อ โปรดเร่งทำลายแหล่งเพาะพันธุ์และพ่นสารเคมีควบคุมโรคตามมาตรการ 1-3-7 ทันที เพื่อกำจัดยุงตัวเต็มวัยที่มีเชื้อก่อนที่จะบินไปกัดและแพร่เชื้อสู่ผู้อื่น
ช่วงเฝ้าระวังผู้ป่วยรายใหม่ (Secondary Gen Period)
เข้าสู่ช่วงสัปดาห์ที่ 2 ของการเกิดโรค ขอให้เฝ้าระวังผู้ป่วยรายใหม่ในพื้นที่อย่างใกล้ชิด และควบคุมค่าดัชนีลูกน้ำ (HI CI) ในหมู่บ้านไม่ให้เกิน 5% เพื่อตัดวงจรการระบาดรอบสอง
{step.label}
{targetDateStr}
แนวทางปฏิบัติตามมาตรฐานกระทรวงสาธารณสุข:
{suggestionText}
คำแนะนำ: ในการอัปโหลดรูปผ่าน Google Form กรุณาพิมพ์ รหัสผู้ป่วย (HN) ให้ถูกต้อง
ข้อมูลเบื้องต้นในการเขียนแบบสอบสวนโรค (รวมผลจาก Day 0 - Day 28)
| รอบการพ่น | วันที่ดำเนินการ | สถานะพ่น 100ม. | เงื่อนไขพิเศษที่ดำเนินการ |
|---|---|---|---|
| ครั้งที่ 1 (Day 1) | {d1.visitDate ? formatThaiDate(d1.visitDate) : '-'} | {d1.isFogging ? |
{d1.sprayVillage && พ่นทั้งหมู่บ้าน} {d1.rainCampaign && รณรงค์หลังฝน} {d1.weeklySpray && พ่นเสริมรายสัปดาห์} {!d1.sprayVillage && !d1.rainCampaign && !d1.weeklySpray && '-'} |
| ครั้งที่ 2 (Day 3) | {d3.visitDate ? formatThaiDate(d3.visitDate) : '-'} | {d3.isFogging ? |
{d3.sprayVillage && พ่นทั้งหมู่บ้าน} {d3.rainCampaign && รณรงค์หลังฝน} {d3.weeklySpray && พ่นเสริมรายสัปดาห์} {!d3.sprayVillage && !d3.rainCampaign && !d3.weeklySpray && '-'} |
| ครั้งที่ 3 (Day 7) | {d7.visitDate ? formatThaiDate(d7.visitDate) : '-'} | {d7.isFogging ? |
{d7.sprayVillage && พ่นทั้งหมู่บ้าน} {d7.rainCampaign && รณรงค์หลังฝน} {d7.weeklySpray && พ่นเสริมรายสัปดาห์} {!d7.sprayVillage && !d7.rainCampaign && !d7.weeklySpray && '-'} |
| ระยะเวลา | HI 100 เมตร | HI ทั้งหมู่บ้าน | CI สถานที่สำคัญ |
|---|---|---|---|
| {row.label} | {getHiStr(row.data.survey100mFound, row.data.survey100mTotal)} | {getHiStr(row.data.surveyCommFound, row.data.surveyCommTotal)} |
{getCiStr(row.data.schoolFound, row.data.schoolTotal, row.data.schoolName)}
{(row.data.otherPlaceTotal) && {getCiStr(row.data.otherPlaceFound, row.data.otherPlaceTotal, row.data.otherPlaceName)}}
|
บ้านเลขที่ที่พบลูกน้ำ
{[d0_2, d1, d3, d7, d14, d21].map(d => d.housesFound100m).filter(Boolean).join(', ') || '- ไม่พบข้อมูล/ไม่ได้ระบุ -'}
สถานะผู้ป่วย Gen 2 (Day 14-21)
{d14.checkGen2 || d21.checkGen2 ?
สถานะยุติการระบาด (Day 28)
{d28.isCaseClosed ?
{d28.aarNote || '- ยังไม่มีการบันทึกข้อความถอดบทเรียน -'}
ประมวลผลตัวชี้วัดความสำเร็จอัตโนมัติ จากฐานข้อมูลการลงพื้นที่จริง
{kpi.target}
{isExpanded && (